package com.vip.common.annotation;

import com.alibaba.fastjson.JSON;
import com.github.therapi.runtimejavadoc.ClassJavadoc;
import com.github.therapi.runtimejavadoc.MethodJavadoc;
import com.github.therapi.runtimejavadoc.RuntimeJavadoc;
import com.vip.common.config.DatasourceProperties;
import com.vip.common.dto.LogPrintDto;
import com.vip.common.entity.SysLog;
import com.vip.common.response.RestResponse;
import com.vip.common.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.Arrays;

/**
 * 日志收集切面，切入点为日志收集注解
 *
 * @author LEON
 * @date 2020/3/13 13:21
 */
@Aspect
@Slf4j
@Component
public class LogCollectionAspect {

    private ThreadLocal<LocalDateTime> dateThreadLocal = new ThreadLocal<>();

    private ThreadLocal<String> logThreadLocal = new ThreadLocal<>();

    @Autowired
    private DatasourceProperties datasourceProperties;

    /**
     * 定义切入点
     */
    @Pointcut("@annotation(com.vip.common.annotation.LogCollection)")
    public void LogPointcut() {
    }

    /**
     * 前置处理，记录请求时间、生成LogID
     */
    @Before(value = "LogPointcut()")
    public void doBefore() {
        this.dateThreadLocal.set(LocalDateTime.now());
        this.logThreadLocal.set(TraceUtils.getTraceId());
    }

    /**
     * 后置通知：方法执行完后进行日志收集
     *
     * @param joinPoint
     * @param res
     */
    @AfterReturning(value = "LogPointcut()", returning = "res")
    public void doAfter(JoinPoint joinPoint, Object res) {
        try {
            MethodSignature ms = (MethodSignature) joinPoint.getSignature();
            Method method = ms.getMethod();
            LogCollection myAnnotation = method.getAnnotation(LogCollection.class);
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            MethodInvocationProceedingJoinPoint jp = (MethodInvocationProceedingJoinPoint) joinPoint;
            // 方法名
            String methodName = jp.getSignature().getName();
            // 注解中的value值
            String value = myAnnotation.value();
            // 方法注释
            String methodComment = "";
            // 如果注解中给了参数则使用注解参数，如果没有给，则从注释中获取
            if (StringUtils.isNotBlank(value)) {
                methodComment = value;
            } else {
                // 从注释中获取方法注释
                ClassJavadoc classJavadoc = RuntimeJavadoc.getJavadoc(jp.getTarget().getClass());
                for (MethodJavadoc methodDoc : classJavadoc.getMethods()) {
                    if (methodDoc.getName().equals(methodName)) {
                        methodComment = methodDoc.getComment().toString();
                        break;
                    }
                }
            }
            // 类全路径
            String classPath = joinPoint.getSignature().getDeclaringTypeName();
            // 操作结果
            Integer resCode = null;
            String resDescription = null, resParams = null;
            if (res instanceof RestResponse) {
                RestResponse response = (RestResponse) res;
                resCode = response.getErrorCode();
                resDescription = response.getDescription();
                resParams = JSON.toJSONString(response.getData());
            }
            SysLog sysLog = new SysLog(UuidUtils.uuid(), logThreadLocal.get(), IPUtils.getIpAddr(request), request.getMethod()
                    , request.getRequestURL().toString(), dateThreadLocal.get(), methodComment, Arrays.toString(joinPoint.getArgs())
                    , classPath + "." + methodName, myAnnotation.type(), myAnnotation.object(), resParams
                    , System.currentTimeMillis() - LocalTimeUtils.localDateTimeToTimeStamp(this.dateThreadLocal.get())
                    , resDescription, resCode, LocalDateTime.now());
            insertSysLog(res, sysLog);
        } catch (Exception e) {
            log.error("收集日志出错！", e);
        }
    }

    /**
     * 打印日志信息，日志数据入库
     *
     * @param sl
     * @return
     */
    private void insertSysLog(Object res, SysLog sl) throws SQLException {
        // 打印日志信息
        log.info(JSON.toJSONString(new LogPrintDto(sl.getCode(), sl.getUrl(), sl.getHttpMethod(), sl.getIpAddr()
                , sl.getMethods(), sl.getParams(), res, LocalTimeUtils.localDateTimeToTimeStamp(sl.getRequestTime())
                , LocalTimeUtils.localDateTimeToTimeStamp(sl.getCreateTime()))));
        Connection conn = PostgreJdbcUtils.getConnection(datasourceProperties.getDriverClassName(), datasourceProperties.getUrl()
                , datasourceProperties.getUsername(), datasourceProperties.getPassword());
        // 日志数据入库
        String sql = "INSERT INTO system_config.sys_log(id, code,ip_addr,http_method, url,request_time,content,params,methods,type" +
                ",object_type,res_params,res_duration,res_description,res_code,create_time) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
        new QueryRunner().update(conn, sql, sl.getId(), sl.getCode(), sl.getIpAddr(), sl.getHttpMethod(), sl.getUrl()
                , sl.getRequestTime(), sl.getContent(), sl.getParams(), sl.getMethods(), sl.getType(), sl.getObjectType(), sl.getResParams()
                , sl.getResDuration(), sl.getResDescription(), sl.getResCode(), sl.getCreateTime());
        conn.close();
    }
}
